package ch.nostromo.tiffanys.clients.xboard.xbif;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import uk.ac.cam.cl.projectcharlie2010.Charlies;
import uk.ac.cam.cl.projectcharlie2010.engines.Engine;
import uk.ac.cam.cl.projectcharlie2010.game.GameController;
import uk.ac.cam.cl.projectcharlie2010.game.GameControllerResult;
import uk.ac.cam.cl.projectcharlie2010.game.player.Player;

import ch.nostromo.lib.controller.NosSettingsException;
import ch.nostromo.tiffanys.clients.xboard.controller.XBoardController;
import ch.nostromo.tiffanys.engines.EngineSettings;
import ch.nostromo.tiffanys.game.GameControllerException;
import ch.nostromo.tiffanys.game.GameControllerListener;
import ch.nostromo.tiffanys.game.game.Game;
import ch.nostromo.tiffanys.game.game.GameIllegalMoveException;
import ch.nostromo.tiffanys.game.game.GameState;
import ch.nostromo.tiffanys.game.move.MoveInput;
import ch.nostromo.tiffanys.game.player.PlayerTools;

public class XBoardInterface implements GameControllerListener {
  private static Logger logger = Logger.getLogger(XBoardInterface.class.getName());

  private GameController gameController = null;

  private int currentEngineColor = Game.COLOR_BLACK;
  
  private int movesTotal = 0;
  private int movesDone = 0;
  
  public XBoardInterface() {
    // Do nothing
  }

  private void doOutput(String msg) {
    logger.info("====> To XBoard: " + msg);
    System.out.println(msg);
  }

  public void playGame() throws NosSettingsException, IOException  {

    String settingsFile = XBoardController.getInstance().getSettings().ENGINE_SETTINGS;
    EngineSettings engineSettings = new EngineSettings(settingsFile);

    Player playerWhite = PlayerTools.createPlayerEngine(XBoardController.getInstance().getSettings().ENGINE, engineSettings);
    Player playerBlack = PlayerTools.createPlayerEngine(XBoardController.getInstance().getSettings().ENGINE, engineSettings);

    BufferedReader keyb = new BufferedReader(new InputStreamReader(System.in));
    doOutput("feature myname=\"Tiffanys " + Charlies.VERSION + "\" " +  XBoardController.getInstance().getSettings().XBOARD_FEATURES);

    while (true) {
      String cmd = keyb.readLine();
      logger.info("<==== From XBoard: " + cmd);

      try {

        // Filter commands
        if (cmd.startsWith("quit")) {
          break;
        } else if (cmd.startsWith("xboard")) {
          // Do nothing

        } else if (cmd.startsWith("protover")) {

          // Do nothing
        } else if (cmd.startsWith("accepted")) {
          // Do nothing

        } else if (cmd.startsWith("new")) {

          logger.info("Creating new game controller.");
          
          gameController = new GameController(playerWhite, playerBlack);
          gameController.addGameControllerListener(this);

          movesDone = 0;
          
          currentEngineColor = Game.COLOR_BLACK;
          
        } else if (cmd.startsWith("random")) {
          // Do nothing

        } else if (cmd.startsWith("level")) {

          setLevel(cmd);

        } else if (cmd.startsWith("sd")) {
          
          setSd(cmd);

        } else if (cmd.startsWith("st")) {
          
          setSt(cmd);
          
        } else if (cmd.startsWith("time")) {
         
          setTime(cmd);

        } else if (cmd.startsWith("otim")) {
          // Do nothing

        } else if (cmd.startsWith("hard")) {
          // Do nothing

        } else if (cmd.startsWith("white")) {
          // Do nothing

        } else if (cmd.startsWith("black")) {
          // Do nothing

        } else if (cmd.startsWith("force")) {
          // Do nothing

        } else if (cmd.startsWith("result")) {
          // Do nothing

        } else if (cmd.startsWith("post")) {
          // Do nothing

        } else if (cmd.startsWith("nopost")) {
          // Do nothing

        } else if (cmd.startsWith("easy")) {
          // Do nothing

        } else if (cmd.startsWith("edit")) {
          // Do nothing

        } else if (cmd.startsWith("#")) {
          // Do nothing

        } else if (cmd.startsWith("computer")) {
          // Do nothing

        } else if (cmd.startsWith("go")) {

          logger.fine("Go received.");
          if (currentEngineColor == gameController.getCurrentColorToMove()) {
            logger.fine("Go ignored, as allready engine turn");
          } else {
            logger.fine("Compute next move");
            gameController.computeNextMove(true);
          }
        } else {

          logger.fine("Move recieved, entering move: " + cmd);
          // Append xboard move to our game
          MoveInput fromXBoard = XBoardMove.xBoard2Tiff(cmd);
          gameController.enterNextMove(fromXBoard);
          
          currentEngineColor = gameController.getCurrentColorToMove();
          

          // Check if game is over, if not, reply with new move
          GameState gameState = gameController.getCurrentGameState();
          if (!gameState.gameOpen()) {
            finishGame(gameState);
          } else {
            logger.fine("Compute next move.");
            gameController.computeNextMove(true);
          }

        }
      } catch (GameIllegalMoveException err) {
        // doOutput("tellusererror MESSAGE: Illegal move: " + cmd);
        // doOutput("Illegal move: " + cmd);
        logger.log(Level.INFO, "IllegalMoveException: ", err);
      } catch (XBoardMoveParsingException err) {
        // doOutput("tellusererror MESSAGE:" + err.getMessage());
        // doOutput("Error:" + err.getMessage());
        logger.log(Level.INFO, "XBoardMoveParsingException: ", err);
      } catch (GameControllerException err) {
        // doOutput("tellusererror MESSAGE:" + err.getMessage());
        // doOutput("Error: " + err.getMessage());
        logger.log(Level.SEVERE, "Unexpected Error: ", err);
      }
    }

  }

  private void finishGame(GameState gameState) {

    if (gameState.whiteMates()) {
      logger.info("Game finished. White mates!");
      doOutput("1-0 {White mates}");
    } else if (gameState.blackMates()) {
      logger.info("Game finished. Black mates!");
      doOutput("0-1 {Black mates}");
    } else if (gameState.remisByMaterial()) {
      logger.info("Game finished. Remis by material!");
      doOutput("1/2-1/2 {Draw by material}");
    } else if (gameState.remisByFifty()) {
      logger.info("Game finished. Remis by 50 moves rule!");
      doOutput("1/2-1/2 {Draw by repetition 50}");
    } else if (gameState.remisByThree()) {
      logger.info("Game finished. Remis by 3 times board repetition!");
      doOutput("1/2-1/2 {Draw by position repetition 3}");
    } else if (gameState.remisByStaleMate()) {
      logger.info("Game finished. Remis by stale mate!");
      doOutput("1/2-1/2 {Draw by Stale Mate}");
    }

  }

  public void computeNextLegalMovesFinished(GameControllerResult gameControllerResult) {
    // Nothing to do

  }

  public void computeNextMoveFinished(GameControllerResult gameControllerResult) {
    try {
      logger.info(gameControllerResult.getEngineStatsOneLine());
      
      doOutput("move " + XBoardMove.tiff2XBoard(gameControllerResult.getSelectedMove()));

      movesDone ++;
      
      if (movesDone == movesTotal) {
        movesDone = 0;
      }
      
      GameState gameState = gameController.getCurrentGameState();

      if (!gameState.gameOpen()) {
        finishGame(gameState);
      }
    } catch (GameControllerException e) {
      logger.log(Level.SEVERE, "Unexpected error", e);
    }
  }

  public void computeNextMoveDepthFinished(GameControllerResult engineResult) {
    // Nothing to do

  }
  
  private void setLevel(String cmd) {
    logger.fine("Parsing level");
    StringTokenizer st = new StringTokenizer(cmd);
    
    st.nextToken(); // Level
    
    String mps = st.nextToken();
    String base = st.nextToken();
    st.nextToken(); // Inc
    
    int movesCount = Integer.valueOf(mps).intValue();
    int seconds = getSeconds(base);
    
    EngineSettings newSettings = new EngineSettings();
    newSettings.GAME_MODE = EngineSettings.GAME_MODE_FIXED_TIME_PER_MOVES;
    newSettings.FIXED_TIME_PER_MOVES_MOVE_COUNT = movesCount;
    newSettings.FIXED_TIME_PER_MOVES_SECONDS = seconds;
    
    this.movesTotal = movesCount;
    this.movesDone = 0;
    
    logger.fine("Setting engine to " + movesCount + " moves in " + seconds + " seconds");
    
    Player playerWhite = PlayerTools.createPlayerEngine(XBoardController.getInstance().getSettings().ENGINE, newSettings);
    Player playerBlack = PlayerTools.createPlayerEngine(XBoardController.getInstance().getSettings().ENGINE, newSettings);

    gameController.setPlayerWhite(playerWhite);
    gameController.setPlayerBlack(playerBlack);
    
  }

  
  private void setTime(String cmd) {
    // Time for probably our engine, though other is still "active"
    try {
      String sec = cmd.substring(5);
      int secTogo = Integer.valueOf(sec).intValue() / 100 -1;
      Engine waiting = gameController.getWaitingEngine();
      if (waiting.getEngineSettings().GAME_MODE == EngineSettings.GAME_MODE_FIXED_TIME_PER_MOVES) {
        int movesToMake = movesTotal - movesDone;

        logger.info("********* Setting remaining time to: " + secTogo + " for " + movesToMake + " moves");
       //  waiting.getEngineSettings().FIXED_TIME_PER_MOVES_MOVE_COUNT = movesToMake;
        waiting.getEngineSettings().FIXED_TIME_PER_MOVES_SECONDS = secTogo;
        waiting.getEngineSettings().FIXED_TIME_PER_MOVES_MOVE_COUNT = movesToMake;
      }
      
    } catch (GameControllerException e) {
      logger.severe(e.getMessage());
    }

  }
  
  private int getSeconds(String base) {
    int dp = base.indexOf(":");
    String minutes = "";
    String seconds = "";
    
    if (dp >= 0) {
      minutes = base.substring(0, dp -1);
      seconds = base.substring(dp + 1);
    } else {
      minutes = base;
    }
    int min = 0;
    if (minutes.length() > 0) {
      min = Integer.valueOf(minutes).intValue();
    }
    
    int sec = 0;
    if (seconds.length() > 0) {
      sec = Integer.valueOf(seconds).intValue();
    }

    return (min * 60) + sec;
      
  }

  private void setSd(String cmd) {
    int depth = Integer.valueOf(cmd).intValue();

    
    EngineSettings newSettings = new EngineSettings();
    newSettings.GAME_MODE = EngineSettings.GAME_MODE_FIXED_DEPTH;
    newSettings.FIXED_DEPTH = depth;
    
    logger.fine("Setting engine to depth "  + depth);
    
    Player playerWhite = PlayerTools.createPlayerEngine(XBoardController.getInstance().getSettings().ENGINE, newSettings);
    Player playerBlack = PlayerTools.createPlayerEngine(XBoardController.getInstance().getSettings().ENGINE, newSettings);

    gameController.setPlayerWhite(playerWhite);
    gameController.setPlayerBlack(playerBlack);
    
  }

  
  private void setSt(String cmd) {
    
    int sec = getSeconds(cmd);
    
    EngineSettings newSettings = new EngineSettings();
    newSettings.GAME_MODE = EngineSettings.GAME_MODE_FIXED_TIME_PER_MOVE;
    newSettings.FIXED_TIME_PER_MOVE = sec;
    
    logger.fine("Setting engine to " + sec + " seconds per move");
    
    Player playerWhite = PlayerTools.createPlayerEngine(XBoardController.getInstance().getSettings().ENGINE, newSettings);
    Player playerBlack = PlayerTools.createPlayerEngine(XBoardController.getInstance().getSettings().ENGINE, newSettings);

    gameController.setPlayerWhite(playerWhite);
    gameController.setPlayerBlack(playerBlack);
    
  }
}
